/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package l1j.jrwz.server.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import l1j.jrwz.configure.Config;
import l1j.jrwz.server.GeneralThreadPool;
import l1j.jrwz.server.IdFactory;
import l1j.jrwz.server.codes.ActionCodes;
import l1j.jrwz.server.datatables.NpcTable;
import l1j.jrwz.server.model.Instance.L1DoorInstance;
import l1j.jrwz.server.model.Instance.L1MonsterInstance;
import l1j.jrwz.server.model.Instance.L1NpcInstance;
import l1j.jrwz.server.model.Instance.L1PcInstance;
import l1j.jrwz.server.model.gametime.L1GameTime;
import l1j.jrwz.server.model.gametime.L1GameTimeAdapter;
import l1j.jrwz.server.model.gametime.L1GameTimeClock;
import l1j.jrwz.server.templates.L1Npc;
import l1j.jrwz.server.templates.L1SpawnTime;
import l1j.jrwz.server.types.Point;

public class L1Spawn extends L1GameTimeAdapter {
    private class SpawnTask implements Runnable {
        private final int _spawnNumber;
        private final int _objectId;

        private SpawnTask(int spawnNumber, int objectId) {
            _spawnNumber = spawnNumber;
            _objectId = objectId;
        }

        @Override
        public void run() {
            doSpawn(_spawnNumber, _objectId);
        }
    }

    private static Logger _log = Logger.getLogger(L1Spawn.class.getName());

    private final L1Npc _template;
    private int _id; // just to find this in the spawn table
    private String _location;
    private int _maximumCount;
    private int _npcid;
    private int _groupId;
    private int _locx;
    private int _locy;
    private int _randomx;
    private int _randomy;
    private int _locx1;
    private int _locy1;
    private int _locx2;
    private int _locy2;
    private int _heading;
    private int _minRespawnDelay;
    private int _maxRespawnDelay;
    private int _mapid;
    private boolean _respaenScreen;
    private int _movementDistance;
    private boolean _rest;
    private int _spawnType;
    private int _delayInterval;
    private L1SpawnTime _time;
    private HashMap<Integer, Point> _homePoint = null; // initでspawnした個々のオブジェクトのホームポイント

    private final List<L1NpcInstance> _mobs = new ArrayList<L1NpcInstance>();

    private static Random _random = new Random();

    private String _name;

    private boolean _initSpawn = false;

    private boolean _spawnHomePoint;

    // private static final int SPAWN_TYPE_NORMAL = 0;

    private static final int SPAWN_TYPE_PC_AROUND = 1;

    private static final int PC_AROUND_DISTANCE = 30;

    private static void closeDoorInCrystalCave(int doorId) {
        for (L1Object object : L1World.getInstance().getObject()) {
            if (object instanceof L1DoorInstance) {
                L1DoorInstance door = (L1DoorInstance) object;
                if (door.getDoorId() == doorId) {
                    door.close();
                }
            }
        }
    }

    public static void doCrystalCave(int npcId) {
        int[] npcId2 = { 46143, 46144, 46145, 46146, 46147, 46148, 46149,
                46150, 46151, 46152 };
        int[] doorId = { 5001, 5002, 5003, 5004, 5005, 5006, 5007, 5008, 5009,
                5010 };

        for (int i = 0; i < npcId2.length; i++) {
            if (npcId == npcId2[i]) {
                closeDoorInCrystalCave(doorId[i]);
            }
        }
    }

    public L1Spawn(L1Npc mobTemplate) {
        _template = mobTemplate;
    }

    private int calcRespawnDelay() {
        int respawnDelay = _minRespawnDelay * 1000;
        if (_delayInterval > 0) {
            respawnDelay += _random.nextInt(_delayInterval) * 1000;
        }
        L1GameTime currentTime = L1GameTimeClock.getInstance().currentTime();
        if (_time != null && !_time.getTimePeriod().includes(currentTime)) { // 指定時間外なら指定時間までの時間を足す
            long diff = (_time.getTimeStart().getTime() - currentTime.toTime()
                    .getTime());
            if (diff < 0) {
                diff += 24 * 1000L * 3600L;
            }
            diff /= 6; // real time to game time
            respawnDelay = (int) diff;
        }
        return respawnDelay;
    }

    /**
     * ホームポイントがある場合は、spawnNumberを基にspawnする。 それ以外の場合は、spawnNumberは未使用。
     */
    protected void doSpawn(int spawnNumber) { // 初期配置
        // 指定時間外であれば、次spawnを予約して終わる。
        if (_time != null
                && !_time.getTimePeriod().includes(
                        L1GameTimeClock.getInstance().currentTime())) {
            executeSpawnTask(spawnNumber, 0);
            return;
        }
        doSpawn(spawnNumber, 0);
    }

    protected void doSpawn(int spawnNumber, int objectId) { // 再出現
        L1NpcInstance mob = null;
        try {
            int newlocx = getLocX();
            int newlocy = getLocY();
            int tryCount = 0;

            mob = NpcTable.getInstance().newNpcInstance(_template);
            synchronized (_mobs) {
                _mobs.add(mob);
            }
            if (objectId == 0) {
                mob.setId(IdFactory.getInstance().nextId());
            } else {
                mob.setId(objectId); // オブジェクトID再利用
            }

            if (0 <= getHeading() && getHeading() <= 7) {
                mob.setHeading(getHeading());
            } else {
                // heading値が正しくない
                mob.setHeading(5);
            }

            int npcId = mob.getNpcTemplate().get_npcId();
            if (npcId == 45488 && getMapId() == 9) { // カスパー
                mob.setMap( (getMapId() + _random.nextInt(2)));
            } else if (npcId == 45601 && getMapId() == 11) { // デスナイト
                mob.setMap( (getMapId() + _random.nextInt(3)));
            } else {
                mob.setMap(getMapId());
            }
            mob.setMovementDistance(getMovementDistance());
            mob.setRest(isRest());
            while (tryCount <= 50) {
                switch (getSpawnType()) {
                    case SPAWN_TYPE_PC_AROUND: // PC周辺に湧くタイプ
                        if (!_initSpawn) { // 初期配置では無条件に通常spawn
                            ArrayList<L1PcInstance> players = new ArrayList<L1PcInstance>();
                            for (L1PcInstance pc : L1World.getInstance()
                                    .getAllPlayers()) {
                                if (getMapId() == pc.getMapId()) {
                                    players.add(pc);
                                }
                            }
                            if (players.size() > 0) {
                                L1PcInstance pc = players.get(_random
                                        .nextInt(players.size()));
                                L1Location loc = pc.getLocation()
                                        .randomLocation(PC_AROUND_DISTANCE,
                                                false);
                                newlocx = loc.getX();
                                newlocy = loc.getY();
                                break;
                            }
                        }
                        // フロアにPCがいなければ通常の出現方法
                    default:
                        if (isAreaSpawn()) { // 座標が範囲指定されている場合
                            Point pt = null;
                            if (_spawnHomePoint
                                    && null != (pt = _homePoint
                                            .get(spawnNumber))) { // ホームポイントを元に再出現させる場合
                                L1Location loc = new L1Location(pt, getMapId())
                                        .randomLocation(
                                                Config.SPAWN_HOME_POINT_RANGE,
                                                false);
                                newlocx = loc.getX();
                                newlocy = loc.getY();
                            } else {
                                int rangeX = getLocX2() - getLocX1();
                                int rangeY = getLocY2() - getLocY1();
                                newlocx = _random.nextInt(rangeX) + getLocX1();
                                newlocy = _random.nextInt(rangeY) + getLocY1();
                            }
                            if (tryCount > 49) { // 出現位置が決まらない時はlocx,locyの値
                                newlocx = getLocX();
                                newlocy = getLocY();
                            }
                        } else if (isRandomSpawn()) { // 座標のランダム値が指定されている場合
                            newlocx = (getLocX() + ((int) (Math.random() * getRandomx()) - (int) (Math
                                    .random() * getRandomx())));
                            newlocy = (getLocY() + ((int) (Math.random() * getRandomy()) - (int) (Math
                                    .random() * getRandomy())));
                        } else { // どちらも指定されていない場合
                            newlocx = getLocX();
                            newlocy = getLocY();
                        }
                }
                mob.setX(newlocx);
                mob.setHomeX(newlocx);
                mob.setY(newlocy);
                mob.setHomeY(newlocy);

                if (mob.getMap().isInMap(mob.getLocation())
                        && mob.getMap().isPassable(mob.getLocation())) {
                    if (mob instanceof L1MonsterInstance) {
                        if (isRespawnScreen()) {
                            break;
                        }
                        L1MonsterInstance mobtemp = (L1MonsterInstance) mob;
                        if (L1World.getInstance().getVisiblePlayer(mobtemp)
                                .size() == 0) {
                            break;
                        }
                        // 画面内にPCが居て出現できない場合は、3秒後にスケジューリングしてやり直し
                        SpawnTask task = new SpawnTask(spawnNumber, mob.getId());
                        GeneralThreadPool.getInstance().schedule(task, 3000L);
                        return;
                    }
                }
                tryCount++;
            }
            if (mob instanceof L1MonsterInstance) {
                ((L1MonsterInstance) mob).initHide();
            }

            mob.setSpawn(this);
            mob.setreSpawn(true);
            mob.setSpawnNumber(spawnNumber); // L1Spawnでの管理番号(ホームポイントに使用)
            if (_initSpawn && _spawnHomePoint) { // 初期配置でホームポイントを設定
                Point pt = new Point(mob.getX(), mob.getY());
                _homePoint.put(spawnNumber, pt); // ここで保存したpointを再出現時に使う
            }

            if (mob instanceof L1MonsterInstance) {
                if (mob.getMapId() == 666) {
                    ((L1MonsterInstance) mob).set_storeDroped(true);
                }
            }
            if (npcId == 45573 && mob.getMapId() == 2) { // バフォメット
                for (L1PcInstance pc : L1World.getInstance().getAllPlayers()) {
                    if (pc.getMapId() == 2) {
                        L1Teleport.teleport(pc, 32664, 32797, 2, 0,
                                true);
                    }
                }
            }

            if (npcId == 46142 && mob.getMapId() == 73 || npcId == 46141
                    && mob.getMapId() == 74) {
                for (L1PcInstance pc : L1World.getInstance().getAllPlayers()) {
                    if (pc.getMapId() >= 72 && pc.getMapId() <= 74) {
                        L1Teleport.teleport(pc, 32840, 32833, 72,
                                pc.getHeading(), true);
                    }
                }
            }
            doCrystalCave(npcId);

            L1World.getInstance().storeObject(mob);
            L1World.getInstance().addVisibleObject(mob);

            if (mob instanceof L1MonsterInstance) {
                L1MonsterInstance mobtemp = (L1MonsterInstance) mob;
                if (!_initSpawn && mobtemp.getHiddenStatus() == 0) {
                    mobtemp.onNpcAI(); // モンスターのＡＩを開始
                }
            }
            if (getGroupId() != 0) {
                L1MobGroupSpawn.getInstance().doSpawn(mob, getGroupId(),
                        isRespawnScreen(), _initSpawn);
            }
            mob.turnOnOffLight();
            mob.startChat(L1NpcInstance.CHAT_TIMING_APPEARANCE); // チャット開始
        } catch (Exception e) {
            _log.log(Level.SEVERE, e.getLocalizedMessage(), e);
        }
    }

    /**
     * SpawnTaskを起動する。
     * 
     * @param spawnNumber
     *            L1Spawnで管理されている番号。ホームポイントが無ければ何を指定しても良い。
     */
    public void executeSpawnTask(int spawnNumber, int objectId) {
        SpawnTask task = new SpawnTask(spawnNumber, objectId);
        GeneralThreadPool.getInstance().schedule(task, calcRespawnDelay());
    }

    public int getAmount() {
        return _maximumCount;
    }

    public int getGroupId() {
        return _groupId;
    }

    public int getHeading() {
        return _heading;
    }

    public int getId() {
        return _id;
    }

    public String getLocation() {
        return _location;
    }

    public int getLocX() {
        return _locx;
    }

    public int getLocX1() {
        return _locx1;
    }

    public int getLocX2() {
        return _locx2;
    }

    public int getLocY() {
        return _locy;
    }

    public int getLocY1() {
        return _locy1;
    }

    public int getLocY2() {
        return _locy2;
    }

    public int getMapId() {
        return _mapid;
    }

    public int getMaxRespawnDelay() {
        return _maxRespawnDelay;
    }

    public int getMinRespawnDelay() {
        return _minRespawnDelay;
    }

    public int getMovementDistance() {
        return _movementDistance;
    }

    public String getName() {
        return _name;
    }

    public int getNpcId() {
        return _npcid;
    }

    public int getRandomx() {
        return _randomx;
    }

    public int getRandomy() {
        return _randomy;
    }

    private int getSpawnType() {
        return _spawnType;
    }

    public L1SpawnTime getTime() {
        return _time;
    }

    public void init() {
        if (_time != null && _time.isDeleteAtEndTime()) {
            // 時間外削除が指定されているなら、時間経過の通知を受ける。
            L1GameTimeClock.getInstance().addListener(this);
        }
        _delayInterval = _maxRespawnDelay - _minRespawnDelay;
        _initSpawn = true;
        // ホームポイントを持たせるか
        if (Config.SPAWN_HOME_POINT
                && Config.SPAWN_HOME_POINT_COUNT <= getAmount()
                && Config.SPAWN_HOME_POINT_DELAY >= getMinRespawnDelay()
                && isAreaSpawn()) {
            _spawnHomePoint = true;
            _homePoint = new HashMap<Integer, Point>();
        }

        int spawnNum = 0;
        while (spawnNum < _maximumCount) {
            // spawnNumは1～maxmumCountまで
            doSpawn(++spawnNum);
        }
        _initSpawn = false;
    }

    private boolean isAreaSpawn() {
        return getLocX1() != 0 && getLocY1() != 0 && getLocX2() != 0
                && getLocY2() != 0;
    }

    private boolean isRandomSpawn() {
        return getRandomx() != 0 || getRandomy() != 0;
    }

    public boolean isRespawnScreen() {
        return _respaenScreen;
    }

    public boolean isRest() {
        return _rest;
    }

    @Override
    public void onMinuteChanged(L1GameTime time) {
        if (_time.getTimePeriod().includes(time)) {
            return;
        }
        synchronized (_mobs) {
            if (_mobs.isEmpty()) {
                return;
            }
            // 指定時間外になっていれば削除
            for (L1NpcInstance mob : _mobs) {
                mob.setCurrentHpDirect(0);
                mob.setDead(true);
                mob.setStatus(ActionCodes.ACTION_Die);
                mob.deleteMe();
            }
            _mobs.clear();
        }
    }

    public void setAmount(int amount) {
        _maximumCount = amount;
    }

    public void setGroupId(int i) {
        _groupId = i;
    }

    public void setHeading(int heading) {
        _heading = heading;
    }

    public void setId(int id) {
        _id = id;
    }

    public void setLocation(String location) {
        _location = location;
    }

    public void setLocX(int locx) {
        _locx = locx;
    }

    public void setLocX1(int locx1) {
        _locx1 = locx1;
    }

    public void setLocX2(int locx2) {
        _locx2 = locx2;
    }

    public void setLocY(int locy) {
        _locy = locy;
    }

    public void setLocY1(int locy1) {
        _locy1 = locy1;
    }

    public void setLocY2(int locy2) {
        _locy2 = locy2;
    }

    public void setMapId(int _mapid) {
        this._mapid = _mapid;
    }

    public void setMaxRespawnDelay(int i) {
        _maxRespawnDelay = i;
    }

    public void setMinRespawnDelay(int i) {
        _minRespawnDelay = i;
    }

    public void setMovementDistance(int i) {
        _movementDistance = i;
    }

    public void setName(String name) {
        _name = name;
    }

    public void setNpcid(int npcid) {
        _npcid = npcid;
    }

    public void setRandomx(int randomx) {
        _randomx = randomx;
    }

    public void setRandomy(int randomy) {
        _randomy = randomy;
    }

    public void setRespawnScreen(boolean flag) {
        _respaenScreen = flag;
    }

    public void setRest(boolean flag) {
        _rest = flag;
    }

    public void setSpawnType(int type) {
        _spawnType = type;
    }

    public void setTime(L1SpawnTime time) {
        _time = time;
    }
}
